/*
 *
 * Copyright (c) 2020 The Raptor Authors. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

#include "raptor-lite/utils/log.h"
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#ifdef _WIN32
#include <Windows.h>
#include <processthreadsapi.h>
#else
#include <sys/syscall.h>
#include <unistd.h>
#endif

#include "raptor-lite/utils/atomic.h"
#include "raptor-lite/utils/color.h"
#include "raptor-lite/utils/time.h"
#include "raptor-lite/utils/sync.h"
#include "src/utils/string.h"

namespace raptor {
namespace {

void log_default_print(LogArgument *args);

AtomicIntptr g_log_function((intptr_t)log_default_print);
AtomicInt32 g_min_level((int32_t)LogLevel::kDebug);
const char *g_desc[] = {"Debug", "Info", "Warn", "Error", "Fatal"};
Color g_fc_table[4]  = {Color::Cyan, Color::Green, Color::Yellow, Color::Red};
Color g_bc_table[4]  = {Color::Black, Color::Black, Color::Black, Color::White};

bool g_use_color = false;
Mutex g_single_mtx;

// 2023-04-12 16:16:00.000 thread_id line Info Message
#define RAPTOR_LOG_FORMAT "%s.%03ld %lu %d %s %s"

#ifndef _WIN32
pid_t GetCurrentThreadId() { return syscall(SYS_gettid); }
#endif

void log_default_print(LogArgument *args) {
    AutoMutex g(&g_single_mtx);
    char time_buffer[64] = {0};

    struct timeval now;
    gettimeofday(&now, NULL);
    time_t timer = now.tv_sec;

#ifdef _WIN32
    struct tm stm;
    if (localtime_s(&stm, &timer)) {
        strcpy(time_buffer, "error:localtime");
    }
#else
    struct tm stm;
    if (!localtime_r(&timer, &stm)) {
        strcpy(time_buffer, "error:localtime");
    }
#endif
    // "%F %T" 2020-05-10 01:43:06
    else if (0 == strftime(time_buffer, sizeof(time_buffer), "%F %T", &stm)) {
        strcpy(time_buffer, "error:strftime");
    }

    // choose color
    Color fc = g_fc_table[static_cast<int>(args->level)];
    Color bc = g_bc_table[static_cast<int>(args->level)];

    char *output_text = NULL;
    raptor_asprintf(&output_text, RAPTOR_LOG_FORMAT, time_buffer,
                    now.tv_usec / 1000, // milliseconds
                    args->threadid, args->line, g_desc[static_cast<int>(args->level)],
                    args->message);

    if (g_use_color) {
        FprintColorTextLine(stderr, fc, bc, output_text);
    } else {
        fprintf(stderr, "%s\n", output_text);
    }
    free(output_text);
    fflush(stderr);
}
} // namespace

// ---------------------------
const char *LogLevelToString(LogLevel level) { return g_desc[static_cast<int>(level)]; }
LogLevel LogLevelByString(const char *str) {
    for (int i = 0; i < 5; i++) {
        if (strcmp(str, g_desc[i]) == 0) {
            return static_cast<LogLevel>(i);
        }
    }
    return LogLevel::kDebug;
}
void LogEnableColor(bool enable) { g_use_color = enable; }

void LogSetLevel(LogLevel level) { g_min_level.Store((int32_t)level); }

void LogSetPrintCallback(LogPrintCallback func) { g_log_function.Store((intptr_t)func); }

void LogFormatPrint(int line, LogLevel level, const char *format, ...) {

    if (static_cast<int32_t>(level) < g_min_level.Load()) return;

    char *message = NULL;
    va_list args;
    va_start(args, format);

#ifdef _WIN32
    int ret = _vscprintf(format, args);
    va_end(args);
    if (ret < 0) {
        return;
    }

    size_t buff_len = (size_t)ret + 1;
    message         = (char *)malloc(buff_len);
    va_start(args, format);
    ret = vsnprintf_s(message, buff_len, _TRUNCATE, format, args);
    va_end(args);
#else
    if (vasprintf(&message, format, args) == -1) { // stdio.h
        va_end(args);
        return;
    }
#endif

    LogArgument tmp;
    tmp.line     = line;
    tmp.level    = level;
    tmp.message  = message;
    tmp.threadid = static_cast<unsigned long>(GetCurrentThreadId());
    ((LogPrintCallback)g_log_function.Load())(&tmp);
    free(message);
}

} // namespace raptor
